package mt3063;
import java.io.BufferedReader;
import java.io.InputStreamReader;

//线段树节点
class Tree{
	public int l; //节点表示区间的左界
	public int r; //节点表示区间的右届
	public int d; //节点数据
	public Tree(int l, int r) {
		this.l = l;
		this.r = r;
	}
}
public class Main {
	//数据长度
	public static int N;
	//线段树原数组，下标从1开始计算
	public static int[] a;
	//线段树，数组表示，长度必须为原数组4倍
	public static Tree[] t;
	// 返回左儿子编号，参数p为父节点编号
	public static int lc(int p) {
		// 左儿子编号p*2
		return p << 1; //位运算提高效率，用p*2也行，更直观
	}
	// 返回右儿子编号，参数p为父节点编号
	public static int rc(int p) {
		// 右儿子编号p*2+1
		return p << 1 | 1; //位运算提高效率，用p*2+1也行，更直观
	}
	//构建线段树
	//参数p节点编号
	//参数l，r表示节点p指向的区间[l,r]
	//初始化时调用build(1,L,R)
	public static void build(int p , int l , int r) {
		//实例化节点
		t[p] = new Tree(l,r);
		//l==r则当前节点为叶子，直接存值
		//叶子节点的l对应原数据的下标
		if(l==r) {
		    //此时递归到了叶子节点
		    t[p].d = a[l]; 
		    return;
		}
		//折半找区间[l,r]的中点
		int mid = (l+r)>>1;   //也可写(l+r)/2
		int lc = lc(p);
		int rc = rc(p);
		//左儿子递归建树(递归建左子树[L,(L+R)/2])
		build(lc,l,mid);
		//右儿子递归建树(递归建右子树[(L+R)/2]+1,R))
		build(rc,mid+1,r);
		//此处是递归回溯节点，当前节点左右子树已建完，依据左右子节点的值来算当前节点的值
		//从下往上传递区间值，放入节点
		//此处决定线段树维护区间和或最大值或最小值
		//参数p是当前节点位置
		pushUp(p);
	}
	//从下往上传递区间值，放入节点
	//此处的p是叶子节点
	private static void pushUp(int p) {
		//树的非叶子节点值是两个子节点的二进制或计算
		t[p].d = t[lc(p)].d | t[rc(p)].d;
	}
	//更新线段树节点(单点)
	//参数p节点编号
	//参数i表示将a[i]更新为v
	public static void update(int p , int i , int v) {
	    //找到了a[i]
	    if(t[p].l==t[p].r&&t[p].l==i) {
	        t[p].d = v; 
	        return;
	    }
	    //折半找区间[t[p].l,t[p].r]的中点
	    int mid = (t[p].l+t[p].r)>>1;
	    int lc = lc(p); //左子节点下标
	    int rc = rc(p); //右子节点下标
	    if(i<=mid)
	        update(lc,i,v); //左子树更新
	    else
	        update(rc,i,v); //右子树更新
	    //从下往上更新区间值，放入节点
	    //此处决定线段树非叶子节点的值
	    pushUp(p);
	}	
	
	//参数p节点编号
	//参数l，r表示求解的区间[l,r]
	public static int query(int p,int l,int r) {
	    //1、查询区间覆盖节点区间，返回节点值（区简解）
	    if(t[p].l>=l&&t[p].r<=r) {
	        return t[p].d;
	    }
	    //折半找节点区间[segTree[k].l,segTree[k].r]的中点
	    int mid = (t[p].l+t[p].r)>>1;
	    //计算节点的左右子节点编号
	    int lc = lc(p);
	    int rc = rc(p);
	    int ans = 0;
	    //l与左子树有重叠，则进入左子树递归查询
	    if(l<=mid)
	        ans = ans | query(lc,l,r);
	    //r与右子树有重叠，则进入左子树递归查询
	    if(r>mid)
	        ans = ans | query(rc,l,r);
	    return ans;
	}	
	//输入优化
	public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	public static StringBuilder sb = new StringBuilder();
	public static void main(String[] args) throws Exception {
		N =Integer.parseInt(br.readLine());
		a = new int[N+1];
		t = new Tree[N*4+1];
		//将字符串中的字符转成整数二进制对应位上的1
		//比如‘a’，对应0000 0001，‘b’对应0000 0010
		//‘a’‘b’合并，二进制或运算后对应0000 0011，二进制有两个1，表示该串2个不同字符
		String s = br.readLine();
		//'a'对应整数
		int k = (int)'a';
		
		for(int i=0;i<s.length();i++) {
			//根据字符，将1左移
			//a下标从1开始
			a[i+1] = 1<<((int)s.charAt(i)-k);
		}
		//构建线段树
		build(1,1,N);
		
		int m = Integer.parseInt(br.readLine());
		while(m>0) {
			m--;
			String[] lineArr = br.readLine().split(" ");
			if(lineArr[0].equals("1")) {
				int pos = Integer.parseInt(lineArr[1]);
				char c = lineArr[2].charAt(0);
				int tmp = 1<<((int)c-k);
				update(1,pos,tmp);
			}else if(lineArr[0].equals("2")) {
				int l = Integer.parseInt(lineArr[1]);
				int r = Integer.parseInt(lineArr[2]);
				//Integer.bitCount是Java自带方法
				//可以计算整数对应的二进制中有多少个1
				int ans = Integer.bitCount(query(1,l,r));
				sb.append(ans);
				sb.append("\n");
			}
		}
		System.out.print(sb.toString());
	}

}
